/*
 *    Copyright 2009-2023 the original author or authors.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *       https://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */
package org.apache.ibatis.builder.xml;

import org.apache.ibatis.builder.*;
import org.apache.ibatis.cache.Cache;
import org.apache.ibatis.executor.ErrorContext;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.mapping.*;
import org.apache.ibatis.parsing.XNode;
import org.apache.ibatis.parsing.XPathParser;
import org.apache.ibatis.reflection.MetaClass;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.TypeHandler;

import java.io.InputStream;
import java.io.Reader;
import java.util.*;

/**
 * 解析mapper
 *
 * @author Clinton Begin
 * @author Kazuki Shimizu
 */
public class XMLMapperBuilder extends BaseBuilder {

  private final XPathParser parser;
  private final MapperBuilderAssistant builderAssistant;
  private final Map<String, XNode> sqlFragments;
  private final String resource;

  @Deprecated
  public XMLMapperBuilder(Reader reader, Configuration configuration, String resource, Map<String, XNode> sqlFragments,
                          String namespace) {
    this(reader, configuration, resource, sqlFragments);
    this.builderAssistant.setCurrentNamespace(namespace);
  }

  @Deprecated
  public XMLMapperBuilder(Reader reader, Configuration configuration, String resource,
                          Map<String, XNode> sqlFragments) {
    this(new XPathParser(reader, true, configuration.getVariables(), new XMLMapperEntityResolver()), configuration,
      resource, sqlFragments);
  }

  public XMLMapperBuilder(InputStream inputStream, Configuration configuration, String resource,
                          Map<String, XNode> sqlFragments, String namespace) {
    this(inputStream, configuration, resource, sqlFragments);
    this.builderAssistant.setCurrentNamespace(namespace);
  }

  public XMLMapperBuilder(InputStream inputStream, Configuration configuration, String resource,
                          Map<String, XNode> sqlFragments) {
    this(new XPathParser(inputStream, true, configuration.getVariables(), new XMLMapperEntityResolver()), configuration,
      resource, sqlFragments);
  }

  private XMLMapperBuilder(XPathParser parser, Configuration configuration, String resource,
                           Map<String, XNode> sqlFragments) {
    super(configuration);
    this.builderAssistant = new MapperBuilderAssistant(configuration, resource);
    this.parser = parser;
    this.sqlFragments = sqlFragments;
    this.resource = resource;
  }

  public void parse() {
    if (!this.configuration.isResourceLoaded(this.resource)) {
      this.configurationElement(this.parser.evalNode("/mapper"));
      this.configuration.addLoadedResource(this.resource);
      this.bindMapperForNamespace();
    }

    this.parsePendingResultMaps();
    this.parsePendingCacheRefs();
    this.parsePendingStatements();
  }

  public XNode getSqlFragment(String refid) {
    return this.sqlFragments.get(refid);
  }

  /**
   * 解析Mapper
   *
   * @param context mapper节点
   */
  private void configurationElement(XNode context) {
    try {
      String namespace = context.getStringAttribute("namespace");
      if (namespace == null || namespace.isEmpty()) {
        throw new BuilderException("Mapper's namespace cannot be empty");
      }
      this.builderAssistant.setCurrentNamespace(namespace);
      // todo 待确认，在xml中，同时配置cache-ref与cache，如果cache-ref的引用已经存在
      // 此时，cache-ref不会抛出异常，即不会放置到incompleteCacheRefs中，此时使用的缓存使用cache-ref
      // 但是，当解析到cache时，cache仍然会工作，此时，会替换掉cache-ref的缓存
      // 如果cache-ref的引用不存在，此时，cache-ref会抛出异常，会进入incompleteCacheRefs等待补偿处理
      // 即使此时有存在cache配置，并且修建及引用了
      // 在补偿处理后，仍然会使用cache-ref的缓存
      // 所以，同时配置cache-ref与cache，其实际缓存不确定，所以，应该禁止同时配置cache-ref与cache

      //引用其它命名空间的缓存配置。
      this.cacheRefElement(context.evalNode("cache-ref"));
      //该命名空间的缓存配置。
      this.cacheElement(context.evalNode("cache"));
      // 老版的，丢弃了
      this.parameterMapElement(context.evalNodes("/mapper/parameterMap"));
      //描述如何从数据库结果集中加载对象，是最复杂也是最强大的元素。
      this.resultMapElements(context.evalNodes("/mapper/resultMap"));
      // 可被其它语句引用的可重用语句块。
      this.sqlElement(context.evalNodes("/mapper/sql"));
      // 映射查询、插入、更新、删除语句。
      this.buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing Mapper XML. The XML location is '" + this.resource + "'. Cause: " + e, e);
    }
  }

  private void buildStatementFromContext(List<XNode> list) {
    if (this.configuration.getDatabaseId() != null) {
      this.buildStatementFromContext(list, this.configuration.getDatabaseId());
    }
    this.buildStatementFromContext(list, null);
  }

  private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
    for (XNode context : list) {
      final XMLStatementBuilder statementParser = new XMLStatementBuilder(this.configuration, this.builderAssistant, context,
        requiredDatabaseId);
      try {
        statementParser.parseStatementNode();
      } catch (IncompleteElementException e) {
        this.configuration.addIncompleteStatement(statementParser);
      }
    }
  }

  private void parsePendingResultMaps() {
    Collection<ResultMapResolver> incompleteResultMaps = this.configuration.getIncompleteResultMaps();
    synchronized (incompleteResultMaps) {
      Iterator<ResultMapResolver> iter = incompleteResultMaps.iterator();
      while (iter.hasNext()) {
        try {
          iter.next().resolve();
          iter.remove();
        } catch (IncompleteElementException e) {
          // ResultMap is still missing a resource...
        }
      }
    }
  }

  private void parsePendingCacheRefs() {
    Collection<CacheRefResolver> incompleteCacheRefs = this.configuration.getIncompleteCacheRefs();
    synchronized (incompleteCacheRefs) {
      Iterator<CacheRefResolver> iter = incompleteCacheRefs.iterator();
      while (iter.hasNext()) {
        try {
          iter.next().resolveCacheRef();
          iter.remove();
        } catch (IncompleteElementException e) {
          // Cache ref is still missing a resource...
        }
      }
    }
  }

  private void parsePendingStatements() {
    Collection<XMLStatementBuilder> incompleteStatements = this.configuration.getIncompleteStatements();
    synchronized (incompleteStatements) {
      Iterator<XMLStatementBuilder> iter = incompleteStatements.iterator();
      while (iter.hasNext()) {
        try {
          iter.next().parseStatementNode();
          iter.remove();
        } catch (IncompleteElementException e) {
          // Statement is still missing a resource...
        }
      }
    }
  }

  private void cacheRefElement(XNode context) {
    if (context != null) {
      this.configuration.addCacheRef(this.builderAssistant.getCurrentNamespace(), context.getStringAttribute("namespace"));
      CacheRefResolver cacheRefResolver = new CacheRefResolver(this.builderAssistant,
        context.getStringAttribute("namespace"));
      try {
        cacheRefResolver.resolveCacheRef();
      } catch (IncompleteElementException e) {
        this.configuration.addIncompleteCacheRef(cacheRefResolver);
      }
    }
  }

  /**
   * 默认情况下，只启用了本地的会话缓存，它仅仅对一个会话中的数据进行缓存。 要启用全局的二级缓存，只需要在你的 SQL 映射文件中添加一行：
   *
   * <pre>{@code
   * <cache/>
   * }</pre>
   * 基本上就是这样。这个简单语句的效果如下:
   * <ul>
   * <li>映射语句文件中的所有 select 语句的结果将会被缓存。</li>
   * <li>映射语句文件中的所有 insert、update 和 delete 语句会刷新缓存。</li>
   * <li>缓存会使用最近最少使用算法（LRU, Least Recently Used）算法来清除不需要的缓存。</li>
   * <li>缓存不会定时进行刷新（也就是说，没有刷新间隔）。</li>
   * <li>缓存会保存列表或对象（无论查询方法返回哪种）的 1024 个引用。</li>
   * <li>缓存会被视为读/写缓存，这意味着获取到的对象并不是共享的，可以安全地被调用者修改，而不干扰其他调用者或线程所做的潜在修改。</li>
   * </ul>
   * 提示 缓存只作用于 cache 标签所在的映射文件中的语句。
   * 如果你混合使用 Java API 和 XML 映射文件，在共用接口中的语句将不会被默认缓存。你需要使用 @CacheNamespaceRef 注解指定缓存作用域。
   * 支持的属性：
   * <pre>{@code
   * <cache
   *   eviction="FIFO"
   *   flushInterval="60000"
   *   size="512"
   *   readOnly="true"/>
   * }</pre>
   *
   * @param context
   */
  private void cacheElement(XNode context) {
    if (context != null) {
      // 自定义缓存，如果有声明，会覆盖缓存行为
      String type = context.getStringAttribute("type", "PERPETUAL");
      Class<? extends Cache> typeClass = this.typeAliasRegistry.resolveAlias(type);
      // 清除策略
      // LRU – 最近最少使用：移除最长时间不被使用的对象。
      // FIFO – 先进先出：按对象进入缓存的顺序来移除它们。
      // SOFT – 软引用：基于垃圾回收器状态和软引用规则移除对象。
      // WEAK – 弱引用：更积极地基于垃圾收集器状态和弱引用规则移除对象。
      String eviction = context.getStringAttribute("eviction", "LRU");
      Class<? extends Cache> evictionClass = this.typeAliasRegistry.resolveAlias(eviction);
      // 刷新间隔）属性可以被设置为任意的正整数，设置的值应该是一个以毫秒为单位的合理时间量。
      // 默认情况是不设置，也就是没有刷新间隔，缓存仅仅会在调用语句时刷新。
      Long flushInterval = context.getLongAttribute("flushInterval");
      // 引用数目）属性可以被设置为任意正整数，要注意欲缓存对象的大小和运行环境中可用的内存资源。默认值是 1024。
      Integer size = context.getIntAttribute("size");
      // 只读）属性可以被设置为 true 或 false。只读的缓存会给所有调用者返回缓存对象的相同实例。
      // 因此这些对象不能被修改。这就提供了可观的性能提升。而可读写的缓存会（通过序列化）返回缓存对象的拷贝。
      // 速度上会慢一些，但是更安全，因此默认值是 false。
      boolean readWrite = !context.getBooleanAttribute("readOnly", false);
      boolean blocking = context.getBooleanAttribute("blocking", false);
      Properties props = context.getChildrenAsProperties();
      this.builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props);
    }
  }

  private void parameterMapElement(List<XNode> list) {
    for (XNode parameterMapNode : list) {
      String id = parameterMapNode.getStringAttribute("id");
      String type = parameterMapNode.getStringAttribute("type");
      Class<?> parameterClass = this.resolveClass(type);
      List<XNode> parameterNodes = parameterMapNode.evalNodes("parameter");
      List<ParameterMapping> parameterMappings = new ArrayList<>();
      for (XNode parameterNode : parameterNodes) {
        String property = parameterNode.getStringAttribute("property");
        String javaType = parameterNode.getStringAttribute("javaType");
        String jdbcType = parameterNode.getStringAttribute("jdbcType");
        String resultMap = parameterNode.getStringAttribute("resultMap");
        String mode = parameterNode.getStringAttribute("mode");
        String typeHandler = parameterNode.getStringAttribute("typeHandler");
        Integer numericScale = parameterNode.getIntAttribute("numericScale");
        ParameterMode modeEnum = this.resolveParameterMode(mode);
        Class<?> javaTypeClass = this.resolveClass(javaType);
        JdbcType jdbcTypeEnum = this.resolveJdbcType(jdbcType);
        Class<? extends TypeHandler<?>> typeHandlerClass = this.resolveClass(typeHandler);
        ParameterMapping parameterMapping = this.builderAssistant.buildParameterMapping(parameterClass, property,
          javaTypeClass, jdbcTypeEnum, resultMap, modeEnum, typeHandlerClass, numericScale);
        parameterMappings.add(parameterMapping);
      }
      this.builderAssistant.addParameterMap(id, parameterClass, parameterMappings);
    }
  }

  private void resultMapElements(List<XNode> list) {
    for (XNode resultMapNode : list) {
      try {
        this.resultMapElement(resultMapNode);
      } catch (IncompleteElementException e) {
        // ignore, it will be retried
      }
    }
  }

  private ResultMap resultMapElement(XNode resultMapNode) {
    return this.resultMapElement(resultMapNode, Collections.emptyList(), null);
  }

  /**
   * 解析ResultMap节点
   * <pre>{@code
   * <resultMap id="detailedBlogResultMap" type="Blog">
   *   <constructor>
   *     <idArg column="blog_id" javaType="int"/>
   *   </constructor>
   *   <result property="title" column="blog_title"/>
   *   <association property="author" javaType="Author">
   *     <id property="id" column="author_id"/>
   *     <result property="username" column="author_username"/>
   *     <result property="bio" column="author_bio"/>
   *     <result property="favouriteSection" column="author_favourite_section"/>
   *   </association>
   *   <collection property="posts" ofType="Post">
   *     <id property="id" column="post_id"/>
   *     <result property="subject" column="post_subject"/>
   *     <association property="author" javaType="Author"/>
   *     <collection property="comments" ofType="Comment">
   *       <id property="id" column="comment_id"/>
   *     </collection>
   *     <collection property="tags" ofType="Tag" >
   *       <id property="id" column="tag_id"/>
   *     </collection>
   *     <discriminator javaType="int" column="draft">
   *       <case value="1" resultType="DraftPost"/>
   *     </discriminator>
   *   </collection>
   * </resultMap>
   * }</pre>
   * <p>
   * 最好逐步建立结果映射。单元测试可以在这个过程中起到很大帮助。
   * 如果你尝试一次性创建像上面示例那么巨大的结果映射，不仅容易出错，难度也会直线上升。
   * 所以，从最简单的形态开始，逐步迭代。而且别忘了单元测试！
   * 有时候，框架的行为像是一个黑盒子（无论是否开源）。
   * 因此，为了确保实现的行为与你的期望相一致，最好编写单元测试。 并且单元测试在提交 bug 时也能起到很大的作用。
   *
   * @param resultMapNode
   * @param additionalResultMappings
   * @param enclosingType
   * @return
   */
  private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings,
                                     Class<?> enclosingType) {
    ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier());
    // 声明的类型
    String type = resultMapNode.getStringAttribute("type", resultMapNode.getStringAttribute("ofType",
      resultMapNode.getStringAttribute("resultType", resultMapNode.getStringAttribute("javaType"))));
    Class<?> typeClass = this.resolveClass(type);
    if (typeClass == null) {
      typeClass = this.inheritEnclosingType(resultMapNode, enclosingType);
    }
    Discriminator discriminator = null;
    List<ResultMapping> resultMappings = new ArrayList<>(additionalResultMappings);
    List<XNode> resultChildren = resultMapNode.getChildren();
    for (XNode resultChild : resultChildren) {
      if ("constructor".equals(resultChild.getName())) {
        // constructor - 用于在实例化类时，注入结果到构造方法中
        //   - idArg - ID 参数；标记出作为 ID 的结果可以帮助提高整体性能
        //   - arg - 将被注入到构造方法的一个普通结果
        this.processConstructorElement(resultChild, typeClass, resultMappings);
      } else if ("discriminator".equals(resultChild.getName())) {
        // 使用结果值来决定使用哪个 resultMap
        //    - case – 基于某些值的结果映射
        //         - 嵌套结果映射 – case 也是一个结果映射，因此具有相同的结构和元素；或者引用其它的结果映射
        discriminator = this.processDiscriminatorElement(resultChild, typeClass, resultMappings);
      } else {
        List<ResultFlag> flags = new ArrayList<>();
        if ("id".equals(resultChild.getName())) {
          flags.add(ResultFlag.ID);
        }
        resultMappings.add(this.buildResultMappingFromContext(resultChild, typeClass, flags));
      }
    }
    // 当前命名空间中的一个唯一标识，用于标识一个结果映射。
    String id = resultMapNode.getStringAttribute("id", resultMapNode.getValueBasedIdentifier());
    String extend = resultMapNode.getStringAttribute("extends");
    // 如果设置这个属性，MyBatis 将会为本结果映射开启或者关闭自动映射。
    // 这个属性会覆盖全局的属性 autoMappingBehavior。默认值：未设置（unset）。
    Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");
    ResultMapResolver resultMapResolver = new ResultMapResolver(this.builderAssistant, id, typeClass, extend, discriminator,
      resultMappings, autoMapping);
    try {
      return resultMapResolver.resolve();
    } catch (IncompleteElementException e) {
      this.configuration.addIncompleteResultMap(resultMapResolver);
      throw e;
    }
  }

  protected Class<?> inheritEnclosingType(XNode resultMapNode, Class<?> enclosingType) {
    if ("association".equals(resultMapNode.getName()) && resultMapNode.getStringAttribute("resultMap") == null) {
      String property = resultMapNode.getStringAttribute("property");
      if (property != null && enclosingType != null) {
        MetaClass metaResultType = MetaClass.forClass(enclosingType, this.configuration.getReflectorFactory());
        return metaResultType.getSetterType(property);
      }
    } else if ("case".equals(resultMapNode.getName()) && resultMapNode.getStringAttribute("resultMap") == null) {
      return enclosingType;
    }
    return null;
  }

  /**
   * 解析constructor节点
   * <p>
   * 构造方法注入允许你在初始化时为类设置属性的值，而不用暴露出公有方法。
   * MyBatis 也支持私有属性和私有 JavaBean 属性来完成注入，但有一些人更青睐于通过构造方法进行注入。 constructor 元素就是为此而生的。
   *
   * <pre>{@code
   * <constructor>
   *    <idArg column="id" javaType="int"/>
   *    <arg column="username" javaType="String"/>
   *    <arg column="age" javaType="_int"/>
   * </constructor>
   * }</pre>
   *
   * @param resultChild
   * @param resultType
   * @param resultMappings
   */
  private void processConstructorElement(XNode resultChild, Class<?> resultType, List<ResultMapping> resultMappings) {
    List<XNode> argChildren = resultChild.getChildren();
    for (XNode argChild : argChildren) {
      List<ResultFlag> flags = new ArrayList<>();
      flags.add(ResultFlag.CONSTRUCTOR);
      if ("idArg".equals(argChild.getName())) {
        flags.add(ResultFlag.ID);
      }
      resultMappings.add(this.buildResultMappingFromContext(argChild, resultType, flags));
    }
  }

  private Discriminator processDiscriminatorElement(XNode context, Class<?> resultType,
                                                    List<ResultMapping> resultMappings) {
    String column = context.getStringAttribute("column");
    String javaType = context.getStringAttribute("javaType");
    String jdbcType = context.getStringAttribute("jdbcType");
    String typeHandler = context.getStringAttribute("typeHandler");
    Class<?> javaTypeClass = this.resolveClass(javaType);
    Class<? extends TypeHandler<?>> typeHandlerClass = this.resolveClass(typeHandler);
    JdbcType jdbcTypeEnum = this.resolveJdbcType(jdbcType);
    Map<String, String> discriminatorMap = new HashMap<>();
    for (XNode caseChild : context.getChildren()) {
      String value = caseChild.getStringAttribute("value");
      String resultMap = caseChild.getStringAttribute("resultMap",
        this.processNestedResultMappings(caseChild, resultMappings, resultType));
      discriminatorMap.put(value, resultMap);
    }
    return this.builderAssistant.buildDiscriminator(resultType, column, javaTypeClass, jdbcTypeEnum, typeHandlerClass,
      discriminatorMap);
  }

  private void sqlElement(List<XNode> list) {
    if (this.configuration.getDatabaseId() != null) {
      this.sqlElement(list, this.configuration.getDatabaseId());
    }
    this.sqlElement(list, null);
  }

  private void sqlElement(List<XNode> list, String requiredDatabaseId) {
    for (XNode context : list) {
      String databaseId = context.getStringAttribute("databaseId");
      String id = context.getStringAttribute("id");
      id = this.builderAssistant.applyCurrentNamespace(id, false);
      if (this.databaseIdMatchesCurrent(id, databaseId, requiredDatabaseId)) {
        this.sqlFragments.put(id, context);
      }
    }
  }

  private boolean databaseIdMatchesCurrent(String id, String databaseId, String requiredDatabaseId) {
    if (requiredDatabaseId != null) {
      return requiredDatabaseId.equals(databaseId);
    }
    if (databaseId != null) {
      return false;
    }
    if (!this.sqlFragments.containsKey(id)) {
      return true;
    }
    // skip this fragment if there is a previous one with a not null databaseId
    XNode context = this.sqlFragments.get(id);
    return context.getStringAttribute("databaseId") == null;
  }

  private ResultMapping buildResultMappingFromContext(XNode context, Class<?> resultType, List<ResultFlag> flags) {
    String property;
    if (flags.contains(ResultFlag.CONSTRUCTOR)) {
      // 构造方法形参的名字
      property = context.getStringAttribute("name");
    } else {
      // 映射到列结果的字段或属性。如果 JavaBean 有这个名字的属性（property），会先使用该属性。
      // 否则 MyBatis 将会寻找给定名称的字段（field）。 无论是哪一种情形，你都可以使用常见的点式分隔形式进行复杂属性导航。
      // 比如，你可以这样映射一些简单的东西：“username”，或者映射到一些复杂的东西上：“address.street.number”。
      property = context.getStringAttribute("property");
    }
    // 数据库中的列名，或者是列的别名。一般情况下，这和传递给 resultSet.getString(columnName) 方法的参数一样。
    String column = context.getStringAttribute("column");
    // 	一个 Java 类的全限定名，或一个类型别名（关于内置的类型别名，可以参考上面的表格）。
    // 	如果你映射到一个 JavaBean，MyBatis 通常可以推断类型。
    // 	然而，如果你映射到的是 HashMap，那么你应该明确地指定 javaType 来保证行为与期望的相一致。
    String javaType = context.getStringAttribute("javaType");
    // JDBC 类型，所支持的 JDBC 类型参见这个表格之后的“支持的 JDBC 类型”。
    // 只需要在可能执行插入、更新和删除的且允许空值的列上指定 JDBC 类型。
    // 这是 JDBC 的要求而非 MyBatis 的要求。如果你直接面向 JDBC 编程，你需要对可以为空值的列指定这个类型。
    String jdbcType = context.getStringAttribute("jdbcType");
    // 用于加载复杂类型属性的映射语句的 ID，它会从 column 属性中指定的列检索数据，作为参数传递给此 select 语句。
    String nestedSelect = context.getStringAttribute("select");
    // 	结果映射的 ID，可以将嵌套的结果集映射到一个合适的对象树中。
    // 	它可以作为使用额外 select 语句的替代方案。它可以将多表连接操作的结果映射成一个单一的 ResultSet。
    // 	这样的 ResultSet 将会将包含重复或部分数据重复的结果集。
    // 	为了将结果集正确地映射到嵌套的对象树中，MyBatis 允许你 “串联”结果映射，以便解决嵌套结果集的问题。
    String nestedResultMap = context.getStringAttribute("resultMap",
      () -> this.processNestedResultMappings(context, Collections.emptyList(), resultType));
    String notNullColumn = context.getStringAttribute("notNullColumn");
    String columnPrefix = context.getStringAttribute("columnPrefix");
    // 我们在前面讨论过默认的类型处理器。使用这个属性，你可以覆盖默认的类型处理器。
    // 这个属性值是一个类型处理器实现类的全限定名，或者是类型别名。
    String typeHandler = context.getStringAttribute("typeHandler");
    String resultSet = context.getStringAttribute("resultSet");
    String foreignColumn = context.getStringAttribute("foreignColumn");
    boolean lazy = "lazy"
      .equals(context.getStringAttribute("fetchType", this.configuration.isLazyLoadingEnabled() ? "lazy" : "eager"));
    Class<?> javaTypeClass = this.resolveClass(javaType);
    Class<? extends TypeHandler<?>> typeHandlerClass = this.resolveClass(typeHandler);
    JdbcType jdbcTypeEnum = this.resolveJdbcType(jdbcType);
    return this.builderAssistant.buildResultMapping(resultType, property, column, javaTypeClass, jdbcTypeEnum, nestedSelect,
      nestedResultMap, notNullColumn, columnPrefix, typeHandlerClass, flags, resultSet, foreignColumn, lazy);
  }

  private String processNestedResultMappings(XNode context, List<ResultMapping> resultMappings,
                                             Class<?> enclosingType) {
    if (Arrays.asList("association", "collection", "case").contains(context.getName())
      && context.getStringAttribute("select") == null) {
      this.validateCollection(context, enclosingType);
      ResultMap resultMap = this.resultMapElement(context, resultMappings, enclosingType);
      return resultMap.getId();
    }
    return null;
  }

  protected void validateCollection(XNode context, Class<?> enclosingType) {
    if ("collection".equals(context.getName()) && context.getStringAttribute("resultMap") == null
      && context.getStringAttribute("javaType") == null) {
      MetaClass metaResultType = MetaClass.forClass(enclosingType, this.configuration.getReflectorFactory());
      String property = context.getStringAttribute("property");
      if (!metaResultType.hasSetter(property)) {
        throw new BuilderException(
          "Ambiguous collection type for property '" + property + "'. You must specify 'javaType' or 'resultMap'.");
      }
    }
  }

  private void bindMapperForNamespace() {
    String namespace = this.builderAssistant.getCurrentNamespace();
    if (namespace != null) {
      Class<?> boundType = null;
      try {
        boundType = Resources.classForName(namespace);
      } catch (ClassNotFoundException e) {
        // ignore, bound type is not required
      }
      if (boundType != null && !this.configuration.hasMapper(boundType)) {
        // Spring may not know the real resource name so we set a flag
        // to prevent loading again this resource from the mapper interface
        // look at MapperAnnotationBuilder#loadXmlResource
        this.configuration.addLoadedResource("namespace:" + namespace);
        this.configuration.addMapper(boundType);
      }
    }
  }

}
